package db

import (
	"container/list"
	"database/sql"
	"d-serv-go/server/core/bean"
	"d-serv-go/server/core/consts"
	"d-serv-go/server/core/local"
	"d-serv-go/server/core/util"
	"d-serv-go/server/core/util/encrypt"
	"errors"
	"fmt"
	"github.com/gofiber/fiber/v2"
	"github.com/kirinlabs/utils/str"
	"strconv"
	"strings"
)

const (
	DB      = "DB"
	DS_NAME = "DS_NAME"
)

func NewContext(ctx *fiber.Ctx) *Context {
	return &Context{
		Ctx:  ctx,
		Bean: bean.NewBean(""),
	}
}

type Context struct {
	Cmpy string
	Ctx  *fiber.Ctx
	Bean *bean.Bean
}


func Ctx() *Context {
	return local.Get(consts.CONTEXT).(*Context)
}

func (ctx *Context) BeginTx(ds string) (*sql.Tx, error) {
	var trans *list.List
	if ctx.Ctx.Locals(consts.TRANSLIST) != nil {
		trans = ctx.Ctx.Locals(consts.TRANSLIST).(*list.List)
	} else {
		trans = list.New()
		ctx.Ctx.Locals(consts.TRANSLIST, trans)
	}
	if trans.Len() > 0 {
		// 判断当前事务链最新的是不是当前数据源的
		if ts, ok := trans.Back().Value.(*bean.Bean); ok && ts != nil && ts.GetStr(DS_NAME) == ds {
			return ts.Get(DB).(*sql.Tx), nil
		}
	}
	if db := GetDb(ds); db != nil {
		tx, err := db.Begin()
		if err != nil {
			return nil, errors.New(fmt.Sprintf("数据源%v开启事务失败,%v", ds, err))
		}
		dsBean := bean.NewBean("")
		dsBean.Set(DS_NAME, ds)
		dsBean.Set(DB, tx)
		trans.PushBack(dsBean)
		return tx, err
	}
	return nil, errors.New(fmt.Sprintf("数据源%v开启事务失败", ds))
}

func (ctx *Context) EndTx() error {
	trans := ctx.Ctx.Locals(consts.TRANSLIST).(*list.List)
	if trans == nil {
		trans = list.New()
		ctx.Ctx.Locals(consts.TRANSLIST, trans)
	}
	if trans.Len() > 0 {
		if ts, ok := trans.Back().Value.(*bean.Bean); ok && ts != nil {
			trans.Remove(trans.Back())
			return ts.Get(DB).(*sql.Tx).Commit()
		}
	}
	return nil
}

// 获取当前事务的bean
func (ctx *Context) getDs() *bean.Bean {
	if trans, ok := ctx.Ctx.Locals(consts.TRANSLIST).(*list.List); ok && trans.Len() > 0 {
		if ts, ok := trans.Back().Value.(*bean.Bean); ok && ts != nil {
			return ts
		}
	} else {
		trans = list.New()
		ctx.Ctx.Locals(consts.TRANSLIST, trans)
	}

	return nil
}

// 得到当前事务的数据库连接,如果没有则返回默认的""
func (ctx *Context) Db(ds string) (*sql.DB, *sql.Tx) {
	// TODO 如果第一个是外部数据源、第二个读平台的字典，
	if dsBean := ctx.getDs(); dsBean != nil {
		tx := dsBean.Get(DB).(*sql.Tx)
		return nil, tx
	}
	return GetDb(ds), nil
}

func (ctx *Context) GetExecutor() SqlExecutorInterface {
	if ds := ctx.getDs(); ds != nil {
		return GetExecutor(ds.GetStr(DB_TYPE, MYSQL))
	}
	return GetExecutor(MYSQL)
}

func (ctx *Context) GetUserBean() *UserBean {
	fctx := ctx.Ctx
	headers := fctx.GetReqHeaders()
	token := headers["X-Xsrf-Token"]
	device := headers["X-Device-Name"]
	if len(token) == 0 {
		token = fctx.Get("X-XSRF-TOKEN", "")
	}
	if len(device) == 0 {
		device = fctx.Get("X-DEVICE-NAME", "")
	}
	if len(token) == 0 {
		token = fctx.Cookies("X-XSRF-TOKEN", "")
	}
	if len(device) == 0 {
		device = fctx.Cookies("X-DEVICE-NAME", "")
	}
	if len(device) == 0 {
		device = "app"
	}
	return ctx.getUserBean2(token, device)
}

func (ctx *Context) getUserBean2(token string, device string) *UserBean {
	fctx := ctx.Ctx
	var userBean *UserBean

	if fctx.Locals(consts.AUTH_NO_SESSION) != nil {
		return nil //(UserBean) getThread(THREAD.USERBEAN);
	}
	if len(token) > 0 { //从session中获取
		userState := UserMgr.GetUserStateByToken(token)
		if userState != nil && userState.IsNotEmpty("USER_LAST_IP") {
			userBean, _ = ctx.SetUser(userState)
			userState.Set("USER_LAST_LOGIN", util.DateUtil.GetDateTime())
			UserMgr.SaveUserState(userState, false)
		} else {                                   //判断是否启用token验证模式
			if len(token) > 0 && len(device) > 0 { // 远程设备方式：根据request头中的token和设备信息进行自动登录处理
				userBean = ctx.GetUserBeanByToken(token, device)
				if userBean != nil { // 自动登录
					ctx.SetOnlineUser(userBean)
				}
			}
		}
	}

	return userBean
}

func (ctx *Context) SetOnlineUser(userBean *UserBean) *bean.UserStateBean {
	//设置线程变量
	ctx.SetCmpy(userBean.GetCmpyCode())

	//setThread(THREAD.USERBEAN, userBean);
	//setThread(THREAD.CMPYCODE, userBean.getCmpyCode());
	//设置session变量
	//if (req != null) {
	userCode := userBean.GetId()
	//	UserMgr.ClearCacheVarMap(userCode); //清除以重新获取用户变量
	//	String ip = RequestUtils.getIpAddr(req);

	time := util.DateUtil.GetDateTime()
	//获取用户状态信息
	//Bean updateState = new Bean();
	userState := UserMgr.GetUserState(userCode) //获取用户状态信息
	if userState == nil {
		userState = bean.NewUserStateBean()
		userState.Set("USER_CODE", userCode)
		userState.Set("USER_FIRST_LOGIN", true) //首次登录标志
		userState.Set("MODIFY_PASSWORD", 1)
		// updateState.set("USER_CODE", userCode);  //为新建作准备
	} else {
		// updateState.setId(userCode); //为修改作准备
	}

	//if (req.getHeader("X-XSRF-TOKEN") == null) { //非令牌模式为正式登录，进行登录数据处理
	userState.Set("USER_LAST_IP", ctx.Ctx.IP())
	userState.Set("USER_LAST_LOGIN", time)

	//updateState.set("USER_LAST_IP", ip);
	//updateState.set("USER_LAST_LOGIN", time);

	//for (String key : new String[] { "USER_LAST_CLIENT", "USER_LAST_OS", "USER_LAST_PCNAME", "USER_LAST_BROWSER" }) {
	//	userState.set(key, RequestUtils.get(req, key, ""));
	//}
	//headers := ctx.Ctx.GetReqHeaders()
	//log.Info(headers)
	device := ctx.Ctx.GetReqHeaders()["X-Device-Name"]
	if (len(device) > 0 && !str.StartsWith(device, "@")) || userState.IsEmpty("USER_TOKEN") {
		token := encrypt.EncryptUtil.Encrypt(userCode+device+userBean.GetCmpyCode()+time, encrypt.MD5) //UserTokenCreator.getInstance().create(userBean);
		userState.Set("USER_TOKEN", token)
		userState.Set("USER_TOKEN", "931d8509dba43c8ad6afeb2f24e15432")
		// updateState.set("USER_TOKEN", token);
	}
	//}
	//boolean bMenu;
	////如果没有启用缓存或者菜单时间为空，则生成菜单
	//if (userState.isEmpty("MENU_TIME") || !Context.getSyConf("SY_COMM_MENU_CACHE", true)) {
	//userState.set("MENU_TIME", time); //设置菜单时间
	//bMenu = true;
	//} else {
	////更新用户状态信息
	//UserMgr.saveUserState(updateState, false); //更新数据库用户状态
	//bMenu = false;
	//}
	UserMgr.SetTokenUserStateCache(userState.GetStr("USER_TOKEN"), userState)
	// 更新userCode->userState的缓存内容
	UserMgr.SetUserTokenCache(userCode, userState)
	ctx.SetStr(consts.SESSIONID, userState.GetStr("USER_TOKEN"))
	//if (bMenu) { //则生成菜单
	//	MenuServ.menuToCache(userCode);
	//	updateState.set("MENU_TIME", time);
	//	ServDao.save(ServMgr.SY_ORG_USER_STATE, updateState);
	//}
	//}
	return userState
}

func (ctx *Context) GetSyMsg(msgCode string, params ...interface{}) string {
	msg := DictMgr.GetName("SY_COMM_MESSAGE", msgCode)
	if len(msg) > 0 {
		return strFormat(msg, params)
	} else {
		if len(params) > 0 {
			return params[0].(string)
		}
	}
	return ""
}

func strFormat(fmtStr string, values ...interface{}) string {
	for i, value := range values {
		fmtStr = strings.ReplaceAll(fmtStr, "{"+strconv.Itoa(i)+"}", fmt.Sprintf("%v", value))
	}
	return fmtStr
}

func (c *Context) SetCmpy(cmpy string) {
	c.Cmpy = cmpy
}

func (ctx *Context) GetCmpy() string {
	return ctx.Cmpy
}

func (c *Context) IsEmpyty(key string) bool {
	return c.Bean.IsEmpty(key)
}

func (ctx *Context) SetStr(key string, value interface{}) {
	ctx.Bean.Set(key, value)
}

func (ctx *Context) GetStr(key string, def ...string) string {
	return ctx.Bean.GetStr(key, def...)
}

func (ctx *Context) Remove(key string) {
	ctx.Bean.Remove(key)
}

func (ctx *Context) SetUser(userState *bean.UserStateBean) (*UserBean, error) {
	ctx.Bean.Set("", userState)

	sessionId := userState.GetStr("USER_TOKEN")
	//addOnlineUser(userState);
	userBean, err := UserMgr.GetUser(userState.GetId())
	local.Set(consts.SESSIONID, sessionId)
	local.Set(consts.USERBEAN, userBean)
	local.Set(consts.CMPYCODE, userBean.GetCmpyCode())
	return userBean, err
}

func (ctx *Context) GetUserBeanByToken(token string, uuid string) *UserBean {
	var userBean *UserBean
	userState := UserMgr.GetUserStateByToken(token)
	if userState != nil {
		userCode := userState.GetStr("USER_CODE")
		//网页版不比对uuid，可以直接使用token
		if len(uuid) == 0 || str.StartsWith(uuid, "@") {
			userBean, _ = UserMgr.GetUser(userCode)
		} else { //手机版需要比对机器码，只有本机的token才可以自动登录
			checkToken := encrypt.EncryptUtil.Encrypt(userCode+uuid, encrypt.MD5)
			if checkToken == token {
				userBean, _ = UserMgr.GetUser(userCode)
			}
		}
	}
	return userBean
}

func (ctx *Context) GetBool(key string, def bool) bool {
	return ctx.Bean.GetBool(key, def)
}
